const ws = require('nodejs-websocket');
// userConn只是保存socket，数据结构为：aaa: socket 连接对象
const userConn = {};
// 保存用户的随机串，只有当用户重新登录时才会更新，socket断开连接和它没关系
// 数据结构为：aaa: 随机串
const userToken = {};
const server = ws.createServer(connection => {
  // result 可能为：
  // (1) Add:/aaa/随机串
  // (2) Auth:/aaa/随机串
  // (3) /aaa/随机串
  connection.on('text', async function (result) {
    // 4、添加用户的 token
    if (result.startsWith('Add:')) {
      result = result.replace(/Add:/, '')
      // account = [aaa, 随机串]
      const account = result.split('/').filter(item => item);

      // 5、如果在 userConn 里找到该账号的连接，说明账号正在另一个设备上登录，那么就把另一个设备踢出，并删掉账号的连接和 另一个设备登录时生成的token
      if (Object.keys(userConn).includes(account[0])) {
        userConn[account[0]].send('该账号已在其它设备登录，你已被踢出');
        userConn[account[0]].close();
        delete userConn[account[0]];
        delete userToken[account[0]];
      }

      // 6、把该账号的 token 改为这次登录时生成的 token
      userToken[account[0]] = account[1]
      return;
    }

    // 10、看这个 token 有没有存在，如果是被挤出的A用户访问，由于该账号的 token 已经被B登录时刷新了，A用户的 token 是找不到的，返回验证错误
    // 如果是 B用户登录成功，前端触发刷新，那么这里是返回验证成功
    const auth = result.startsWith('Auth:');
    result = result.replace(/Auth:/, '')
    const account = result.split('/').filter(item => item);

    // 14、访问文件时的验证，只返回 true 或 false，被挤出的A用户，带着被删除的 token 访问，肯定返回 false，只有新登录的 B用户，带着新生成的 token 才会返回 true
    if (userToken[account[0]] == account[1]) {
      connection.send(`${auth ? 'Auth:' : ''}true`)
      return true;
    }
    connection.send(`${auth ? 'Auth:' : ''}false`)
    return false;
  });

  connection.on('close', async function (code) {
    // 17、当前端用户离开时，会触发 close 事件，视为关闭浏览器下线，删掉这个 socket 连接，但是不删除 随机串
    // 随机串的替换和删除只有在 on text 事件中，消息为 Add: 开头时才会触发，触发情况一般是：
    // (1) 用户删除浏览器缓存，清除了 cookie 中的 authId，给他返回了登录页，他从新登录
    // (2) A用户登录了 aaa 账号，B用户也登录 aaa 账号，B用户的随机串把A用户的给覆盖了
    // 所以只要没有人挤号，A用户 cookie 中 authId 也永远在就不会删除这个 token
    // 哪怕 aaa 这个号一年都没有被登录，userToken 中 aaa 用户的 token 都是一直在的
    // 要想实现一天、一周、一个月强制用户重新登录，那么就设置 cookie 的有效期，不过容易被篡改
    // 另一种解决办法是设置一个定时器，去定时删除 userToken 中的 token，也可以放到 redis 中 设置过期时间
    delete userConn[connection.path]
  });

  connection.on('error', function (code) {
  })
}).listen(36013);

// 当有客户端连接时，会触发该事件。
// result 和 ws.createServer 产生的 connection 是同一个对象，它们有一个close函数，调用就可以让服务端主动关闭这个socket
server.on("connection", async function (result) {
  // 16、首页会建立 socket 连接，把该用户的 socket 连接保存起来，path = / 是后端启动时 serve-static 连接的，不用保存
  // 一般来说 path = /aaa/随机串
  if (result.path == '/') return;
  // 把 result.path 搞成 [aaa, 随机串]，然后取下标 0 就是 aaa
  userConn[result.path.split('/').filter(item => item)[0]] = result;
});

// 前提：前端带着用户名和随机字符串发送 socket 请求
// 用户首次登录，保存账号和随机字符串到 userConn   假设是 /zhangsan/a
// 同样的账号被另一个用户登录   假设是 /zhangsan/b
// 先根据 zhangsan 从 userConn 找到已登录的 /zhangsan/a，由于 value 就是 connection，直接给客户端发送被踢出的消息
// 然后把 /zhangsan/a 从 userConn 中删除，替换为 /zhangsan/b，实现踢出的操作
// 仅仅只有提示还不够，如果用户删除掉 websocket 代码，直接调用接口，或者直接输入视频地址，例如：http://xxx.xxx.xxx.xxx:3001/a.mp4
// 所以就需要对 express 的静态目录进行二次开发
//                                                                      authId 就是前端生成的用户名 + 随机字符串 = /zhangsan/a
// 大概思路就是，先创建一个 websocket 对象，每次请求过来，都根据 cookie 里的 authId 去找 socket 验证，socket 从 userConn 中找 authId
// 来返回 true 或 false



// 为什么要加随机字符串？
// 比如用户名是 zhangsan，A用户 是被 B用户挤出去了，但是A用户把 cookie authId 设置成 zhangsan，然后A用户直接输入视频地址，跳过了websocket的连接过程
// 然后静态目录去找 socket 验证，发现 zhangsan 是存在的，于是便返回视频文件
// 没有达到踢出的效果，所以要有一个东西来区分不同的设备

// 随机字符串是加密的，md5(loginName + 年-月-日, qwezxc)
// qwezxc是盐